﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.EnterpriseManagement;
using Microsoft.EnterpriseManagement.Common;
using Microsoft.EnterpriseManagement.Configuration;


namespace ScsmIncidentLibrary
{
    public class ServiceManagerGateway : IDisposable
    {
        private ConfigMgr _configMgr;
        private EnterpriseManagementGroup _mg;
        private ManagementPack _mp;
        private ManagementPack _smp;
        private ManagementPack _svcMgrIncidentMgmtPack;
        private static ManagementPackClass _incidentClass;
        private static ManagementPackClass _actionLogClass;
        private static ManagementPackClass _microsoftADUserClass;
        private static ManagementPackRelationship _createdByUserRelationshipType;
        private static ManagementPackRelationship _affectedUserRelationshipType;
        private static ManagementPackRelationship _actionLogRelationshipType;
        private static ManagementPackTypeProjection _systemWorkItemIncidentProjectionType;
        private static ManagementPackElementCollection<ManagementPackEnumeration> _enums;
        private static ManagementPackEnumeration _closedStatusEnum;
        private static ManagementPackEnumeration _activeStatusEnum;
        private static ManagementPackEnumeration _taskExecutedEnum;
        private static string incidentSearch = @"
                <Criteria xmlns=""http://Microsoft.EnterpriseManagement.Core.Criteria/"">
                    <Reference Id=""System.WorkItem.Library"" PublicKeyToken=""{0}"" Version=""{1}"" Alias=""WorkItem""/>
                      <Expression>
                        <SimpleExpression>
                          <ValueExpressionLeft>
                            <Property>$Context/Property[Type='WorkItem!System.WorkItem']/Id$</Property>
                          </ValueExpressionLeft>
                          <Operator>Equal</Operator>
                          <ValueExpressionRight>
                            <Value>{2}</Value>
                          </ValueExpressionRight>
                        </SimpleExpression>
                      </Expression>
                    </Criteria>";
        private static string userSearch = @"
                <Criteria xmlns=""http://Microsoft.EnterpriseManagement.Core.Criteria/"">
                   <Expression>
                    <And>
                    <Expression>
                    <SimpleExpression>
                      <ValueExpressionLeft>
                        <Property>$Target/Property[Type='Microsoft.AD.User']/UserName$</Property>
                      </ValueExpressionLeft>
                      <Operator>Equal</Operator>
                      <ValueExpressionRight>
                        <Value>{1}</Value>
                      </ValueExpressionRight>
                    </SimpleExpression>
                    </Expression>
                    <Expression>
                    <SimpleExpression>
                      <ValueExpressionLeft>
                        <Property>$Target/Property[Type='Microsoft.AD.User']/Domain$</Property>
                      </ValueExpressionLeft>
                      <Operator>Equal</Operator>
                      <ValueExpressionRight>
                        <Value>{0}</Value>
                      </ValueExpressionRight>
                    </SimpleExpression>
                    </Expression>
                    </And>
                  </Expression>
                </Criteria>";

        private EnterpriseManagementGroup EMG
        {
            get
            {
                if (_mg == null)
                {
                    _mg = new EnterpriseManagementGroup(_configMgr.ServiceManagerURL());
                }
                return _mg;
            }
        }

        private ManagementPack SystemWorkItemIncidentLibraryMgmtPack
        {
            get
            {
                if (_mp == null)
                {
                    _mp = EMG.ManagementPacks.GetManagementPack(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemIncidentLibraryMgmtPackGuid()));
                }
                return _mp;
            }
        }

        private ManagementPack ServiceManagerMgmtPack
        {
            get
            {
                if (_svcMgrIncidentMgmtPack == null)
                {
                    _svcMgrIncidentMgmtPack = EMG.ManagementPacks.GetManagementPack(
                        new Guid(_configMgr.ServiceManagerIncidentManagementLibraryMgmtPackGuid()));
                }
                return _svcMgrIncidentMgmtPack;
            }
        }

        private ManagementPack SystemWorkItemLibraryMgmtPack
        {
            get
            {
                if (_smp == null)
                {
                    _smp = EMG.ManagementPacks.GetManagementPack(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemLibraryMgmtPackGuid()));
                }
                return _smp;
            }
        }

        private ManagementPackClass MicrosoftADUserClass
        {
            get
            {
                if (_microsoftADUserClass == null)
                {
                    _microsoftADUserClass = GetManagementPackClass(
                        new Guid(_configMgr.ServiceManagerMicrosoftADUserClassGuid()));
                }
                return _microsoftADUserClass;
            }
        }

        private ManagementPackClass IncidentClass
        {
            get
            {
                if(_incidentClass == null)
                {
                    _incidentClass = GetManagementPackClass(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemIncidentClassGuid()));
                }
                return _incidentClass;
            }
        }

        private ManagementPackClass ActionLogClass
        {
            get
            {
                if (_actionLogClass == null)
                {
                    _actionLogClass = GetManagementPackClass(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemTroubleTicketActionLogClassGuid()));
                }
                return _actionLogClass;
            }
        }

        private ManagementPackRelationship CreatedByUserRelationshipType
        {
            get
            {
                if (_createdByUserRelationshipType == null)
                {
                    _createdByUserRelationshipType = GetRelationshipClass(
                    new Guid(_configMgr.ServiceManagerSystemWorkItemCreatedByUserRelationshipTypeGuid()));
                }
                return _createdByUserRelationshipType;
            }
        }

        private ManagementPackRelationship AffectedUserRelationshipType
        {
            get
            {
                if (_affectedUserRelationshipType == null)
                {
                    _affectedUserRelationshipType = GetRelationshipClass(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemAffectedUserRelationshipTypeGuid()));
                }
                return _affectedUserRelationshipType;
            }
        }

        private ManagementPackRelationship ActionLogRelationshipType
        {
            get
            {
                if (_actionLogRelationshipType == null)
                {
                    _actionLogRelationshipType = GetRelationshipClass(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemTroubleTicketHasActionLogRelationshipTypeGuid()));
                }
                return _actionLogRelationshipType;
            }
        }

        private ManagementPackTypeProjection SystemWorkItemIncidentProjectionType
        {
            get
            {
                if (_systemWorkItemIncidentProjectionType == null)
                {
                    _systemWorkItemIncidentProjectionType = 
                        GetManagementPackTypeProjection(
                        new Guid(_configMgr.ServiceManagerSystemWorkItemIncidentProjectionTypeGuid()));
                }
                return _systemWorkItemIncidentProjectionType;
            }
        }
        
        private ManagementPackElementCollection<ManagementPackEnumeration> ManagementPackEnumeration
        {
            get
            {
                if (_enums == null)
                {
                    _enums = SystemWorkItemIncidentLibraryMgmtPack.GetEnumerations();
                }
                return _enums;
            }
        }

        private ManagementPackEnumeration TaskExecutedEnum
        {
            get
            {
                if (_taskExecutedEnum == null)
                {
                    _taskExecutedEnum = SystemWorkItemLibraryMgmtPack.GetEnumerations().GetItem("System.WorkItem.ActionLogEnum.TaskExecuted");//TODO Config
                }
                return _taskExecutedEnum;
            }
        }

        private ManagementPackEnumeration ClosedStatusEnum
        {
            get
            {
                if (_closedStatusEnum == null)
                {
                    _closedStatusEnum = ManagementPackEnumeration.GetItem("IncidentStatusEnum.Closed");//TODO Config
                }
                return _closedStatusEnum;
            }
        }

        private ManagementPackEnumeration ActiveStatusEnum
        {
            get
            {
                if (_activeStatusEnum == null)
                {
                    _activeStatusEnum = ManagementPackEnumeration.GetItem("IncidentStatusEnum.Active");//TODO Config
                }
                return _activeStatusEnum;
            }
        }



        public ServiceManagerGateway() : this(new ConfigMgr())
        {
        }

        public ServiceManagerGateway(ConfigMgr configMgr)
        {
            if (configMgr == null)
            {
                this._configMgr = new ConfigMgr();
            }
            else
            {
                this._configMgr = configMgr;
            }
            
                        
        }



        public virtual void CloseIncidentByAffectedUser(
            string currentUserDomainName, 
            string currentUserName, 
            string incidentId)
        {
            UpdateByAffectedUserOnly(incidentId, currentUserDomainName, currentUserName, ClosedStatusEnum);
        }

        public virtual void ActivateIncidentByAffectedUser(
            string currentUserDomainName, 
            string currentUserName, 
            string incidentId)
        {
            UpdateByAffectedUserOnly(incidentId, currentUserDomainName, currentUserName, ActiveStatusEnum);
        }

        public virtual void CreateIncident(string userDomainNameForIncidentCreatedBy, string userNameForIncidentCreatedBy, string title, string description)
        {
            string continuedMessage = _configMgr.ServiceManagerContinuedInActionLogMessage();
            int descriptionMaxLength = _configMgr.ServiceManagerIncidentDescriptionMaxLength();
            int titleMaxLength = _configMgr.ServiceManagerIncidentTitleMaxLength();
            int actionLogMaxlength = _configMgr.ServiceManagerIncidentActionLogMaxLength();

            string descriptionToUse = null;

            ManagementPackClass incidentMgmtPackClass = IncidentClass;

            EnterpriseManagementObjectProjection incidentProjection = 
                GetEnterpriseManagementObjectProjection(incidentMgmtPackClass);

            incidentProjection.Object[incidentMgmtPackClass, "Id"].Value = 
                _configMgr.ServiceManagerCreateIncidentIdPrefixFormat();
            incidentProjection.Object[incidentMgmtPackClass, "Source"].Value =
                GetEnumeration(new Guid(_configMgr.ServiceManagerCreateIncidentSourceEnumGuid()));
            incidentProjection.Object[incidentMgmtPackClass, "Status"].Value =
                GetEnumeration(new Guid(_configMgr.ServiceManagerCreateIncidentStatusEnumGuid()));
            incidentProjection.Object[incidentMgmtPackClass, "Impact"].Value =
                GetEnumeration(new Guid(_configMgr.ServiceManagerCreateIncidentImpactEnumGuid()));
            incidentProjection.Object[incidentMgmtPackClass, "Urgency"].Value =
                GetEnumeration(new Guid(_configMgr.ServiceManagerCreateIncidentUrgencyEnumGuid()));

            EnterpriseManagementObject createdByUser = 
                SetUserOnCreation(
                incidentProjection, 
                userDomainNameForIncidentCreatedBy, 
                userNameForIncidentCreatedBy);

            descriptionToUse = GetDescriptionOnCreation(
                createdByUser, 
                userDomainNameForIncidentCreatedBy, 
                userNameForIncidentCreatedBy, 
                description);

            SetTitleOnCreation(title, titleMaxLength, continuedMessage, 
                createdByUser, actionLogMaxlength, incidentMgmtPackClass, 
                incidentProjection);

            SetDescriptionOnCreation(descriptionToUse, descriptionMaxLength, 
                continuedMessage, createdByUser, actionLogMaxlength, 
                incidentMgmtPackClass, incidentProjection);

            incidentProjection.Commit();
        }

        private void SetDescriptionOnCreation(
            string descriptionToUse, 
            int descriptionMaxLength, 
            string continuedMessage, 
            EnterpriseManagementObject createdByUser, 
            int actionLogMaxlength, 
            ManagementPackClass incidentMgmtPackClass, 
            EnterpriseManagementObjectProjection incidentProjection)
        {
            if (descriptionToUse.Length <= descriptionMaxLength)
            {
                incidentProjection.Object[incidentMgmtPackClass, "Description"].Value = descriptionToUse;
            }
            else
            {
                incidentProjection.Object[incidentMgmtPackClass, "Description"].Value = descriptionToUse.Substring(0, (descriptionMaxLength - continuedMessage.Length)) + continuedMessage;//TODO Config

                AddActionLogEntries(
                    incidentProjection,
                    "Description continued",//TODO Config
                    descriptionToUse.Substring((descriptionMaxLength - continuedMessage.Length), (descriptionToUse.Length - (descriptionMaxLength - continuedMessage.Length))),
                    createdByUser == null ? null : createdByUser.DisplayName,
                    actionLogMaxlength);
            }
        }

        private void SetTitleOnCreation(
            string title, 
            int titleMaxLength, 
            string continuedMessage, 
            EnterpriseManagementObject createdByUser, 
            int actionLogMaxlength, 
            ManagementPackClass incidentMgmtPackClass,
            EnterpriseManagementObjectProjection incidentProjection)
        {
            if (title.Length <= titleMaxLength)
            {
                incidentProjection.Object[incidentMgmtPackClass, "Title"].Value = title;
            }
            else
            {
                incidentProjection.Object[incidentMgmtPackClass, "Title"].Value = title.Substring(0, (titleMaxLength - continuedMessage.Length)) + continuedMessage;
                AddActionLogEntries(
                    incidentProjection,
                    "Title continued",//TODO Config
                    title.Substring((titleMaxLength - continuedMessage.Length), (title.Length - (titleMaxLength - continuedMessage.Length))),
                    createdByUser == null ? null : createdByUser.DisplayName,
                    actionLogMaxlength);
            }
        }

        private string GetDescriptionOnCreation(
            EnterpriseManagementObject createdByUser, 
            string userDomainNameForIncidentCreatedBy, 
            string userNameForIncidentCreatedBy, 
            string description)
        {
            if (true == true && createdByUser == null)//TODO Config
            {
                return "Created by unknown user " + GetUserName(userDomainNameForIncidentCreatedBy, userNameForIncidentCreatedBy) + ".\n" + description;//TODO Config
            }
            return description;
        }

        private EnterpriseManagementObject SetUserOnCreation(
            EnterpriseManagementObjectProjection incidentProjection, 
            string userDomainNameForIncidentCreatedBy, 
            string userNameForIncidentCreatedBy)
        {
            EnterpriseManagementObject createdByUser = GetUser(userDomainNameForIncidentCreatedBy, userNameForIncidentCreatedBy);

            if (createdByUser != null)
            {
                incidentProjection.Add(createdByUser, CreatedByUserRelationshipType.Target);//TODO config
                incidentProjection.Add(createdByUser, AffectedUserRelationshipType.Target);//TODO config
            }
            return createdByUser;
        }
        
        private void AddActionLogEntries(
            EnterpriseManagementObjectProjection objectProjection, 
            string title, 
            string description, 
            string userName, 
            int maxLengthToUseForDescription)
        {
            int loopIndex = 0;

            while (description.Length > 0 &&
                description.Length > ((loopIndex * maxLengthToUseForDescription) - 1))
            {
                string desc = description.Substring(((loopIndex) * maxLengthToUseForDescription),
                    (((loopIndex + 1) * maxLengthToUseForDescription) - 1) > description.Length ? //if chunk if greater than string
                    (description.Length - ((loopIndex) * maxLengthToUseForDescription)) : //just get rest of string
                    (((loopIndex + 1) * maxLengthToUseForDescription) - 1)); //otherwise get another chunk

                AddActionLogEntry(objectProjection,
                    (title + (loopIndex > 0 ? " " + (loopIndex + 1).ToString() : "")),
                    desc,
                    userName);

                loopIndex++;
            }
        }

        private void AddActionLogEntry(
            EnterpriseManagementObjectProjection objectProjection,
            string title,
            string description,
            string userName)
        {
            ManagementPackClass typeActionLog = ActionLogClass;
            
            EnterpriseManagementObjectProjection objectActionLog = 
                GetEnterpriseManagementObjectProjection(typeActionLog);
            
            objectActionLog.Object[typeActionLog, "Id"].Value = Guid.NewGuid().ToString();
            objectActionLog.Object[typeActionLog, "EnteredDate"].Value = DateTime.Now.ToUniversalTime();
            objectActionLog.Object[typeActionLog, "Description"].Value = description;
            objectActionLog.Object[typeActionLog, "Title"].Value = title;
            if (!string.IsNullOrEmpty(userName))
            {
                objectActionLog.Object[typeActionLog, "EnteredBy"].Value = userName;
            }
            objectActionLog.Object[typeActionLog, "ActionType"].Value = TaskExecutedEnum;
            
            objectProjection.Add(objectActionLog, ActionLogRelationshipType.Target);
             
        }

        private void UpdateByAffectedUserOnly(
            string incidentId, 
            string currentUserDomainName, 
            string currentUserName,
            ManagementPackEnumeration status)
        {            
            IObjectProjectionReader<EnterpriseManagementObject> objectProjection = GetIncidents(incidentId);

            if (objectProjection.Count > 0)
            {
                ManagementPackClass incidentClass = IncidentClass;
                
                EnterpriseManagementObjectProjection resultTypeProjection = objectProjection.ElementAt(0);

                IComposableProjection affectedUser = resultTypeProjection[AffectedUserRelationshipType.Target].ElementAt(0);

                string currentAffectedUser = affectedUser.Object.Name.Replace('.', '\\'); 

                string activeUser = GetUserName(currentUserDomainName, currentUserName);

                if (currentAffectedUser.Equals(activeUser, StringComparison.InvariantCultureIgnoreCase) &&
                    resultTypeProjection.Object[incidentClass, "Status"].Value != ClosedStatusEnum)
                {
                    resultTypeProjection.Object[incidentClass, "Status"].Value = status;
                    resultTypeProjection.Overwrite();
                }
                else
                {
                    throw new ApplicationException("Current User is not the affected user. Update will not occur.");
                }
            }
        }

        private IObjectProjectionReader<EnterpriseManagementObject> GetIncidents(string Id)
        {
            string query = CreateIncidentCriteriaXml(Id);
            
            ObjectProjectionCriteria criteria = GetObjectProjectionCriteria(
                query,
                SystemWorkItemIncidentProjectionType,
                SystemWorkItemIncidentLibraryMgmtPack);

            return GetEnterpriseTypeProjections(criteria);
        }

        private EnterpriseManagementObject GetUser(string userDomain, string userName)
        {
            string userCriteria = CreateUserCriteriaXml(userDomain, userName);

            ManagementPack mpWindows = GetManagementPack(SystemManagementPack.Windows);
            
            EnterpriseManagementObjectCriteria criteria =
                GetEnterpriseManagementObjectCriteria(userCriteria, MicrosoftADUserClass, mpWindows);

            IEnumerable<EnterpriseManagementObject> users =
               (IEnumerable<EnterpriseManagementObject>)GetEnterpriseObjects(criteria);
            
            return users.FirstOrDefault();
        }

        private string GetUserName(string domainName, string userName)
        {
            return domainName + @"\" + userName;
        }

        private string CreateIncidentCriteriaXml(string incidentSearchString)
        {                    
            return String.Format(
                incidentSearch, 
                SystemWorkItemIncidentLibraryMgmtPack.KeyToken, 
                SystemWorkItemIncidentLibraryMgmtPack.Version.ToString(), 
                incidentSearchString.ToString());
        }

        private string CreateUserCriteriaXml(string userDomain, string username)
        {                 
            string userCriteria = String.Format(userSearch, userDomain, username);

            return userCriteria;
        }





        private IObjectProjectionReader<EnterpriseManagementObject> GetEnterpriseTypeProjections(ObjectProjectionCriteria criteria)
        {
            return EMG.EntityObjects.GetObjectProjectionReader<EnterpriseManagementObject>(
                criteria,
                ObjectQueryOptions.Default);
        }

        private IObjectReader<EnterpriseManagementObject> GetEnterpriseObjects(EnterpriseManagementObjectCriteria criteria)
        {
            return EMG.EntityObjects.GetObjectReader<EnterpriseManagementObject>(criteria, ObjectQueryOptions.Default);
        }

        private ObjectProjectionCriteria GetObjectProjectionCriteria(
            string criteria,
            ManagementPackTypeProjection projection,
            ManagementPack managementPack)
        {
            return new ObjectProjectionCriteria(
                criteria,
                projection,
                managementPack,
                EMG);
        }

        private EnterpriseManagementObjectCriteria GetEnterpriseManagementObjectCriteria(
            string criteria,
            ManagementPackClass managementPackClass,
            ManagementPack managementPack)
        {
            return new EnterpriseManagementObjectCriteria(criteria, managementPackClass, managementPack, EMG);
        }

        private ManagementPack GetManagementPack(SystemManagementPack systemManagementPack)
        {
            return EMG.ManagementPacks.GetManagementPack(systemManagementPack);
        }

        private ManagementPack GetManagementPack(Guid Id)
        {
            return EMG.ManagementPacks.GetManagementPack(Id);
        }

        private ManagementPackClass GetManagementPackClass(string className, ManagementPack managementPack)
        {
            return EMG.EntityTypes.GetClass(className, managementPack);
        }

        private ManagementPackClass GetManagementPackClass(Guid Id)
        {
            return EMG.EntityTypes.GetClass(Id);
        }

        private ManagementPackEnumeration GetEnumeration(Guid Id)
        {
            return EMG.EntityTypes.GetEnumeration(Id);
        }

        private ManagementPackRelationship GetRelationshipClass(Guid Id)
        {
            return EMG.EntityTypes.GetRelationshipClass(Id);
        }

        private EnterpriseManagementObjectProjection GetEnterpriseManagementObjectProjection(ManagementPackClass managementPackClass)
        {
            return new EnterpriseManagementObjectProjection(EMG, managementPackClass);
        }

        private ManagementPackTypeProjection GetManagementPackTypeProjection(Guid Id)
        {
            return EMG.EntityTypes.GetTypeProjection(Id);
        }


        #region IDisposable Members

        public void Dispose()
        {
            if (EMG != null)
            {
                EMG.Dispose();
            }
        }

        #endregion
    }
}
